#include "vl_props.h"
#include "vl_prop_writer.h"
#include "vl_prop_reader.h"
#include "vl_structure.h"
#include <list>
#include <unordered_map>

using namespace VL;


namespace VL {
// using MAP_TYPE=std::map<std::string, VLProp>;
using MAP_TYPE=SimpleHashMap<std::string, VLProp>;

MAP_TYPE* pickMap(const std::shared_ptr<void>& ptr) {
	return std::static_pointer_cast<MAP_TYPE, void>(ptr).get();
}

}


VLProp::VLProp(std::initializer_list<std::pair<const std::string, VLProp>> props): 
	dataPointer(new MAP_TYPE(props)), 
	extraPointer(nullptr), 
	type(VLPropType::Map),
	valueType(VLPropValueType::Void) {}

VLProp& VLProp::operator [] (const std::string& key) {
	if(this->type != VLPropType::Map) {
		this->dataPointer = std::make_shared<MAP_TYPE>();
		this->type = VLPropType::Map;
		this->valueType = VLPropValueType::Void;
	}

	auto map = pickMap(this->dataPointer);
	auto iter = map->find(key);
	if(iter != map->end()) {
		return iter->second;
	} else {
		auto ret = map->emplace(key, VLProp());
		return ret->second;
	}
}

const VLProp& VLProp::operator [] (const std::string& key) const {
	if(this->type != VLPropType::Map) {
		return VLProp::emptyPtop;
	}
	auto map = pickMap(this->dataPointer);
	auto iter = map->find(key);
	if(iter != map->end()) {
		return iter->second;
	}
	return VLProp::emptyPtop;
}

void VLProp::_uniqueMap() {
	auto map = pickMap(this->dataPointer);
	for(auto iter = map->begin() ; iter != map->end() ; iter++) {
		iter->second.unique();
	}
	return;
}

const bool VLProp::_checkMapEqual(const VLProp& prop) const {
	auto map = pickMap(this->dataPointer);
	auto otherMap = pickMap(prop.dataPointer);
	if(map->size() != otherMap->size()) {
		return false;
	}
	auto endIter = otherMap->end();
	for(auto iter = map->begin() ; iter != map->end() ; iter++) {
		auto findIter = otherMap->find(iter->first);
		if(findIter == endIter) {
			return false;
		}
		if(iter->second != findIter->second) {
			return false;
		}
	}
	return true;
}

const size_t VLProp::_getMapSize() const {
	auto map = pickMap(this->dataPointer);
	return map->size();
}

const void VLProp::_copyMap(VLProp& targetProp) const {
	auto map = pickMap(this->dataPointer);
	targetProp.dataPointer = std::make_shared<MAP_TYPE>(*map);
	targetProp.type = VLPropType::Map;
	targetProp.valueType = VLPropValueType::Void;
}

const void VLProp::visitMap(VLPropMapVistor visitor) const {
	if(visitor && this->type == VLPropType::Map) {
		auto map = pickMap(this->dataPointer);
		for(auto iter = map->begin() ; iter != map->end() ; iter ++) {
			if(iter->second.type == VLPropType::Empty) {
				continue;
			}
			if(!visitor(iter->first, iter->second)) {
				break;
			}
		}
	}
}

const void VLProp::_printMap(std::string margin) const {
	auto map = pickMap(this->dataPointer);
	for(auto iter = map->begin() ; iter != map->end() ; iter ++) {
		auto extra = iter->second.extraPointer.get();
		if(extra != nullptr) {
			for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
				printf("%s# %s\n", margin.c_str(), iter->c_str());
			}
		}
		switch(iter->second.type) {
			case VLPropType::Node:
				printf("%s%s: ", margin.c_str(), iter->first.c_str());
				iter->second._printNode("");
				printf("\n");
				break;
			case VLPropType::Array:
				printf("%s%s: \n", margin.c_str(), iter->first.c_str());
				iter->second._printArray(margin + '\t');
				break;
			case VLPropType::Map:
				printf("%s%s: \n", margin.c_str(), iter->first.c_str());
				iter->second._printMap(margin + '\t');
				break;
			case VLPropType::Empty:
				printf("%s%s: \n", margin.c_str(), iter->first.c_str());
				break;
		}
	}
}

const void VLProp::_writeMapVLC(VLPropWriter& writer) const {
	writer.write((uint8_t)this->type & 0xFF);
	auto extra = this->extraPointer.get();
	if(extra != nullptr) {
		auto extraSize = extra->size();
		writer.write(extraSize, 2);
		for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
			writer.writeStrWithSize(*iter);
		}
	} else {
		writer.write(0, 2);
	}
	auto map = pickMap(this->dataPointer);
	auto size = map->size();
	writer.write(size, 2);
	for(auto iter = map->begin() ; iter != map->end() ; iter ++) {
		auto & prop = iter->second;
		writer.writeStrWithSize(iter->first);
		switch(prop.type) {
			case VLPropType::Node:
				prop._writeNodeVLC(writer);
				break;
			case VLPropType::Array:
				prop._writeArrayVLC(writer);
				break;
			case VLPropType::Map:
				prop._writeMapVLC(writer);
				break;
			case VLPropType::Empty:
				prop._writeEmptyVLC(writer);
				break;
		}
	}
}

const void VLProp::_readMapVLC(VLPropReader& reader) {
	this->dataPointer = std::make_shared<MAP_TYPE>();
	this->extraPointer.reset();
	this->type = VLPropType::Map;
	this->valueType = VLPropValueType::Void;
	this->_readExtraVLC(reader);
	auto map = pickMap(this->dataPointer);
	auto size = reader.readNumber<uint16_t>();
	std::string key;
	VLProp child;
	for(uint16_t i = 0 ; i < size ; i ++) {
		reader.readStrWithSize(key);
		VLProp::readVLC(reader, child);
		map->emplace(key, child);
	}
}


const void VLProp::_writeMapVLP(VLPropWriter& writer, std::string margin) const {
	auto map = pickMap(this->dataPointer);
	for(auto iter = map->begin() ; iter != map->end() ; iter ++) {
		auto extra = iter->second.extraPointer.get();
		if(extra != nullptr) {
			for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
				writer.writeStr("%s# %s\n", margin.c_str(), iter->c_str());
			}
		}
		switch(iter->second.type) {
			case VLPropType::Node:
				writer.writeStr("%s%s: ", margin.c_str(), iter->first.c_str());
				iter->second._writeNodeVLP(writer, "");
				writer.write('\n');
				break;
			case VLPropType::Array:
				writer.writeStr("%s%s: \n", margin.c_str(), iter->first.c_str());
				iter->second._writeArrayVLP(writer, margin + '\t');
				break;
			case VLPropType::Map:
				writer.writeStr("%s%s: \n", margin.c_str(), iter->first.c_str());
				iter->second._writeMapVLP(writer, margin + '\t');
				break;
			case VLPropType::Empty:
				writer.writeStr("%s%s: \n", margin.c_str(), iter->first.c_str());
				break;
		}
	}
}